Skip to main content

12 进程的创建

复制结构

dup_task_struct:

  • 调用 alloc_task_struct_node 分配一个 task_struct 结构;
  • 调用 alloc_thread_stack_node 创建内核栈,这里面调用 __vmalloc_node_range 分配一个连续的 THREAD_SIZE 的内存空间,赋值给 task_struct 的 void *stack 成员变量;
  • 调用 arch_dup_task_struct(struct task_struct *dst, struct task_struct *src),将 task_struct 进行复制,即调用 memcpy;
  • 调用 setup_thread_stack 设置 thread_info。

copy_creds 权限相关:

  • 调用 prepare_creds,准备一个新的 struct cred *new;
  • 接着 p->cred = p->real_cred = get_cred(new),将新进程的权限指向新的 cred。

copy_process 设置调度相关:

  • 调用 __sched_fork,将 on_rq 设为 0,初始化 sched_entity,将 exec_start、sum_exec_runtime、prev_sum_exec_runtime、vruntime 都设为 0;
  • 设置进程的状态 p->state = TASK_NEW;
  • 初始化优先级 prio、normal_prio、static_prio;
  • 设置调度类,如果是普通进程,就设置为 p->sched_class = &fair_sched_class;
  • 调用调度类的 task_fork 函数,对于 CFS 来讲,就是调用 task_fork_fair。

copy_process 初始化与文件和文件系统相关的变量:

copy_process 初始化与信号相关的变量:

copy_process 复制进程内存空间:

唤醒新进程

wake_up_new_task:

  • state = TASK_RUNNING; activate 用调度类将当前子进程入队列
  • enqueue_entiry 中会调用 update_curr 更新运行统计量, 再加入队列
  • 调用 check_preempt_curr 看是否能抢占, 若 task_fork_fair 中已设置 sysctl_sched_child_runs_first, 直接返回, 否则进一步比较并调用 resched_curr 做抢占标记
  • 若父进程被标记会被抢占, 则系统调用 fork 返回过程会调度子进程

用户态创建线程

内核态和用户态里分别有一个维护线程的 pthread 结构,每一个进程或者线程都有一个 task_struct 结构。

创建线程栈 allocate_stack:

  • 如果线程属性里设置过栈的大小,需要把设置的值拿出来;
  • 防止栈的访问越界,在栈的末尾有一块空间 guardsize,一旦访问到这里就错误了;
  • 线程栈是在进程的堆里面创建的,线程栈使用的内存块需要有一个缓存;
  • 缓存里面没有,就需要调用 __mmap 创建一块新的,如果要在堆里面 malloc 一块内存,比较大的话用 __mmap;
  • 线程栈是自顶向下生长的,每个线程要有一个 pthread 结构,这个结构也是放在栈的空间里;
  • 计算出 guard 内存的位置,调用 setup_stack_prot 设置这块内存的是受保护的;
  • 填充 pthread 这个结构里面的成员变量 stackblock、stackblock_size、guardsize、specific;
  • 将这个线程栈放到 stack_used 链表中。

内核态创建任务

create_thread 函数创建线程:

  • 设置 clone_flags 标志位, 调用 __clone
  • clone 系统调用返回时, 应该要返回到新线程上下文中, 因此 __clone 将参数和指令位置压入栈中, 返回时从该函数开始执行

用户态执行线程

所有线程在用户态有一个统一入口,用户的函数执行完毕之后,会释放这个线程相关的数据。如果这是最后一个线程了,就直接退出进程,__free_tcb 释放 pthread。